/*
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package commandhandlers;

import input.IRCCommand;
import interfaces.CommandHandler;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.NumberFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import main.Bot;
import main.CommonMethods;
import main.Constants;
import main.Main;
import main.UserCentral;

import org.apache.log4j.Logger;
import org.jibble.pircbot.Colors;

import output.IRCOutput;
import timers.ArmyTimer;
import tools.InputType;
import tools.StringTools;
import tools.UtopiaTime;
import utopiaintelpackage.database.SoTtoDB;
import utopiaintelpackage.sot.SoT;
import botobjects.Army;
import botobjects.Attack;
import database.DBConnectionFactory;
import exceptions.UtoDateException;

/**
 * A class that handles army related commands
 * 
 * 
 */
public class ArmyHandler extends CommandHandler {
   private static ArmyHandler INSTANCE = null;

   /**
    * Finds the first free general. For example general 2 might be home, but 1, 3 & 4 are out.
    * 
    * @param user
    * @return
    */
   public static Integer findFreeGeneral(final String user) {
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  int free = 1;
	  try {
		 ps = DBConnectionFactory.getConnectionHandler().getConnection()
			   .prepareStatement("SELECT armyno FROM army WHERE mainnick = ?");
		 ps.setString(1, user);
		 rs = ps.executeQuery();

		 if (rs.isAfterLast())
			return free;
		 while (rs.next()) {
			if (rs.getInt("armyno") == free) {
			   ++free;
			} else {
			   break;
			}
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return free;
   }

   /**
    * Lists users with armies coming home within the hour
    * 
    * @param utotime
    * @return
    */
   public static List<String> getArmiesReturningThisTick(final String utotime) {
	  List<String> out = new LinkedList<String>();

	  UtopiaTime ut;
	  try {
		 ut = new UtopiaTime(utotime);
	  } catch (UtoDateException e) {
		 Logger.getLogger("Error").error("", e);
		 return out;
	  }
	  String output = "";
	  for (Army army : Army.getArmies(Constants.ARMY_GET_RETURNING_THIS_TICK, new Date(ut.getTime()))) {
		 output += army.getMainNick() + ", ";
		 if (output.length() > 300) {
			out.add(output);
			output = "";
		 }
	  }
	  if (!"".equals(output)) {
		 out.add(output);
	  }
	  return out;
   }

   public static final ArmyHandler getInstance() {
	  if (INSTANCE == null) {
		 INSTANCE = new ArmyHandler();
	  }
	  return INSTANCE;
   }

   public static final ArmyHandler newInstance() {
	  INSTANCE = null;
	  return getInstance();
   }

   private ArmyHandler() {
   }

   /**
    * Used to edit various values of an army, such as the mod off sent etc.
    */
   private List<String> editArmy(final int armyin, final int mo, final int generals, final Long armyhomedelay,
		 final String mainnick) {
	  List<String> out = new LinkedList<String>();
	  Timestamp timeOfAttack = null;
	  int army = armyin;
	  PreparedStatement ps = null;
	  ResultSet rs = null;
	  try {
		 boolean hasArmy = false;
		 if (army == 0) { // 0 means no specific army number to edit, so it edits the one added last
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("SELECT armyno,armyadded FROM " + "army WHERE mainnick = ? ORDER BY armyadded DESC");
			ps.setString(1, mainnick);
			rs = ps.executeQuery();
			if (rs.next()) {
			   army = rs.getInt("armyno");
			   timeOfAttack = new Timestamp(rs.getTimestamp("armyadded").getTime());
			   hasArmy = true;
			} else {
			   out.add("You have no armies registered");
			}
		 } else {
			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("SELECT armyadded FROM " + "army WHERE mainnick = ? AND armyno = ?");
			ps.setString(1, mainnick);
			ps.setInt(2, army);
			rs = ps.executeQuery();
			if (rs.next()) {
			   timeOfAttack = new Timestamp(rs.getTimestamp("armyadded").getTime());
			   hasArmy = true;
			} else {
			   out.add("No such army number");
			}
		 }

		 try {
			ps.close();
		 } catch (final Exception e) {
		 }

		 if (hasArmy) {
			if (armyhomedelay == 0) { // no change to return time
			   ps = DBConnectionFactory
					 .getConnectionHandler()
					 .getConnection()
					 .prepareStatement(
						   "UPDATE army SET " + "mo = ?, generals = ?, armyhome = armyhome WHERE armyno = ? AND mainnick = ?");
			   ps.setInt(1, mo);
			   ps.setInt(2, generals);
			   ps.setInt(3, army);
			   ps.setString(4, mainnick);
			} else {
			   ps = DBConnectionFactory
					 .getConnectionHandler()
					 .getConnection()
					 .prepareStatement(
						   "UPDATE army SET " + "mo = ?, generals = ?, armyhome = ? WHERE armyno = ? AND mainnick = ?");
			   ps.setInt(1, mo);
			   ps.setInt(2, generals);
			   ps.setTimestamp(3, new Timestamp(GregorianCalendar.getInstance().getTimeInMillis() + armyhomedelay));
			   ps.setInt(4, army);
			   ps.setString(5, mainnick);

			   Bot.getInstance().getTimerHandler().removeArmyTimer(mainnick, army);
			   Bot.getInstance().getTimerHandler().addArmyTimer(new ArmyTimer(mainnick, army, armyhomedelay));
			}

			int i = ps.executeUpdate();

			try {
			   ps.close();
			} catch (final Exception e) {
			}

			ps = DBConnectionFactory.getConnectionHandler().getConnection()
				  .prepareStatement("UPDATE attacks SET " + "mo = ?, timeofhit = timeofhit WHERE nick = ? AND timeofhit = ?");
			ps.setInt(1, mo);
			ps.setString(2, mainnick);
			ps.setTimestamp(3, timeOfAttack);
			ps.executeUpdate();

			if (i == 1) {
			   out.add("Army updated successfully");
			}
		 }
	  } catch (final SQLException e) {
		 Logger.getLogger("Error").error("", e);
	  } finally {
		 try {
			rs.close();
		 } catch (final Exception e) {
		 }
		 try {
			ps.close();
		 } catch (final Exception e) {
		 }
	  }
	  return out;
   }

   @Override
   public void handle(final IRCCommand command, final IRCOutput output) {
   }

   public void handleArmies(final IRCCommand command, final IRCOutput out) {
	  List<Army> armyList = new LinkedList<Army>();
	  if (command.size() == 1) {
		 armyList = Army.getArmies(Constants.ARMY_GET_ALL_INTRA);
		 out.addOutput(printArmiesReturning(armyList, true));
	  } else if (command.size() == 2) {
		 if (command.isInt(1)) {
			armyList = Army.getArmies(Constants.ARMY_GET_NUMBER_LIMITED_INTRA, Integer.parseInt(command.get(1)));
			out.addOutput(printArmiesReturning(armyList, false));
		 } else if (command.isDouble(1)) {
			armyList = Army.getArmies(Constants.ARMY_GET_TIME_LIMITED_INTRA, new Date(
				  (long) Double.parseDouble(command.get(1)) * 3600000));
			out.addOutput(printArmiesReturning(armyList, false));
		 } else if (command.get(1).equalsIgnoreCase("home")) {
			String output = Colors.RED + "People with all gens home: ";
			for (Army army : Army.getArmies(Constants.ARMY_GET_ALL_HOME)) {
			   if (output.length() > 350) {
				  out.addOutput(output.substring(0, output.length() - 2));
				  output = "";
			   }
			   output += Colors.BLUE + army.getMainNick() + Colors.NORMAL + ", ";
			}
			if (output.length() > 0) {
			   out.addOutput(output.substring(0, output.length() - 2));
			}
		 } else if (command.validate(1, InputType.KDLOC)) {
			armyList = Army.getArmies(Constants.ARMY_GET_ALL_KD, command.get(1));
			out.addOutput(printArmiesReturning(armyList, true));
		 }
	  } else if (command.size() == 3) {
		 if (command.get(1).equalsIgnoreCase("home") && command.get(2).equalsIgnoreCase("full")) {
			String output = Colors.RED + "People with all gens home: ";
			for (Army army : Army.getArmies(Constants.ARMY_GET_ALL_HOME)) {
			   if (output.length() > 350) {
				  out.addOutput(output.substring(0, output.length() - 2));
				  output = "";
			   }
			   output += Colors.BLUE + army.getMainNick() + Colors.NORMAL + ", ";
			}
			if (output.length() > 0) {
			   out.addOutput(output.substring(0, output.length() - 2));
			}

			Map<String, Integer> generalsMap = new HashMap<String, Integer>();
			Map<String, Integer> modOffMap = new HashMap<String, Integer>();
			for (Army army : Army.getArmies(Constants.ARMY_GET_ALL_INTRA)) {
			   if (generalsMap.containsKey(army.getMainNick())) {
				  generalsMap.put(army.getMainNick(), generalsMap.get(army.getMainNick()) + army.getGenerals());
				  modOffMap.put(army.getMainNick(),
						modOffMap.get(army.getMainNick()) + (int) (army.getMO() / (1 + 0.03 * army.getGenerals())));
			   } else {
				  generalsMap.put(army.getMainNick(), army.getGenerals());
				  modOffMap.put(army.getMainNick(), (int) (army.getMO() / (1 + 0.03 * army.getGenerals())));
			   }
			}
			for (Entry<String, Integer> e : generalsMap.entrySet()) {
			   if (e.getValue() < 4) {
				  generalsMap.put(e.getKey(), 4 - e.getValue());
				  SoT sot = SoTtoDB.getSoT(CommonMethods.getProv(e.getKey()));
				  if (sot != null) {
					 modOffMap.put(e.getKey(), Math.max(0, sot.getMo() - modOffMap.get(e.getKey())));
				  }
			   }
			}

			output = Colors.RED + "People with some gens home: ";
			for (final Entry<String, Integer> entry : generalsMap.entrySet()) {
			   if (entry.getValue() < 4) {
				  String mo = "";
				  if (modOffMap.containsKey(entry.getKey())) {
					 int total = modOffMap.get(entry.getKey());
					 int moout = entry.getValue();
					 mo = total > moout ? String.valueOf(total - moout) : "0";
				  }
				  if (output.length() > 350) {
					 out.addOutput(output.substring(0, output.length() - 2));
					 output = "";
				  }
				  output += Colors.BLUE + entry.getKey() + Colors.NORMAL + "(" + entry.getValue() + ")" + "(" + mo + "), ";
			   }
			}
			if (output.length() > 0) {
			   out.addOutput(output.substring(0, output.length() - 2));
			}
		 } else if (command.validate(1, InputType.KDLOC) && command.isInt(2)) {
			armyList = Army.getArmies(Constants.ARMY_GET_NUMBER_LIMITED_KD, command.get(1), Integer.parseInt(command.get(2)));
			out.addOutput(printArmiesReturning(armyList, false));
		 } else if (command.validate(1, InputType.KDLOC) && command.isDouble(2)) {
			armyList = Army.getArmies(Constants.ARMY_GET_TIME_LIMITED_KD, command.get(1), Double.parseDouble(command.get(2)));
			out.addOutput(printArmiesReturning(armyList, false));
		 }
	  }
   }

   public void handleArmy(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1) {
		 for (Army army : Army.getArmies(Constants.ARMY_GET_SINGLE_USER,
			   UserCentral.getInstance().getOnlineUser(command.getSender()))) {
			out.addOutput(army.getIRCOutput(Constants.ARMY_REGULAR_OUTPUT));
		 }
		 if (out.getOutput().isEmpty()) {
			out.addOutput(command.getSender() + "'s armies are home");
		 }
	  } else if (command.size() == 2) {
		 String user = CommonMethods.getUserFromDBFuzzyMatching(command.get(1));
		 if ("".equals(user)) {
			out.addOutput("No such user");
		 } else {
			for (Army army : Army.getArmies(Constants.ARMY_GET_SINGLE_USER, user)) {
			   out.addOutput(army.getIRCOutput(Constants.ARMY_REGULAR_OUTPUT));
			}
			if (out.getOutput().isEmpty()) {
			   out.addOutput(user + "'s armies are home");
			}
		 }
	  } else if (command.size() == 3) {
		 if (command.isInt(1) && (command.isDouble(2) || command.isInt(2))) {
			Long armyhometime = GregorianCalendar.getInstance().getTimeInMillis()
				  + (long) (Double.parseDouble(command.get(2)) * Main.getBotSettings().getTickLength() * 60 * 1000);
			Army army = new Army(UserCentral.getInstance().getOnlineUser(command.getSender()), Integer.parseInt(command.get(1)),
				  armyhometime, GregorianCalendar.getInstance().getTimeInMillis(), 0, 4, "0");
			if (army.saveToDB()) {
			   Bot.getInstance().sendMessage(
					 command.getChannel(),
					 Colors.OLIVE + "Army added for " + Colors.RED + army.getMainNick() + Colors.OLIVE + ", returns in "
						   + Colors.BLUE + UtopiaTime.compareTimes(army.getArmyHome()));
			   if (UserCentral.getInstance().getOnlineUser(command.getSender()).equals(army.getMainNick())) {
				  // don't ask if someone added an attack for another user
				  out.addOutput("Use !mo <offense> <generals> to add more info to the attack");
			   }
			}
		 }
	  } else {
		 if (command.isInt(2) && (command.isDouble(3) || command.isInt(3))) {
			String user = CommonMethods.getUserFromDB(command.remove(1), false);
			if ("".equals(user)) {
			   out.addOutput("No such user");
			} else {
			   Long armyhometime = GregorianCalendar.getInstance().getTimeInMillis()
					 + (long) (Double.parseDouble(command.get(2)) * Main.getBotSettings().getTickLength() * 60 * 1000);
			   Army army = new Army(user, Integer.parseInt(command.get(1)), armyhometime, GregorianCalendar.getInstance()
					 .getTimeInMillis(), 0, 4, "0");
			   if (army.saveToDB()) {
				  Bot.getInstance().sendMessage(
						Main.getBotSettings().getMainChannel(),
						Colors.OLIVE + "Army added for " + Colors.RED + army.getMainNick() + Colors.OLIVE + ", returns in "
							  + Colors.BLUE + UtopiaTime.compareTimes(army.getArmyHome()));
				  if (UserCentral.getInstance().getOnlineUser(command.getSender()).equals(army.getMainNick())) {
					 // don't ask if someone added an attack for another user
					 out.addOutput("Use !mo <offense> <generals> to add more info to the attack");
				  }
			   }
			}
		 }
	  }
   }

   public void handleArmytime(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 1) {
		 for (Army army : Army.getArmies(Constants.ARMY_GET_SINGLE_USER,
			   UserCentral.getInstance().getOnlineUser(command.getSender()))) {
			out.addOutput(army.getIRCOutput(Constants.ARMY_LOCAL_TIME_OUTPUT,
				  UserCentral.getInstance().getOnlineUser(command.getSender())));
		 }
	  } else if ("".equals(CommonMethods.getUserFromDBFuzzyMatching(command.get(1)))) {
		 out.addOutput("No such user");
	  } else {
		 for (Army army : Army
			   .getArmies(Constants.ARMY_GET_SINGLE_USER, CommonMethods.getUserFromDBFuzzyMatching(command.get(1)))) {
			out.addOutput(army.getIRCOutput(Constants.ARMY_LOCAL_TIME_OUTPUT,
				  UserCentral.getInstance().getOnlineUser(command.getSender())));
		 }
	  }
   }

   public void handleLasthit(final IRCCommand command, final IRCOutput out) {
	  String searchFor = command.getFullMessage();
	  String user = CommonMethods.getUserFromDB(searchFor, false);
	  if (!"".equals(user)) {
		 searchFor = user;
	  } else if (searchFor.matches(".*\\([0-9]{1,2}(:)[0-9]{1,2}\\)")) {
		 // ignores the kd loc atm
		 searchFor = searchFor.substring(0, searchFor.indexOf('(')).trim();
	  }
	  Attack attack = Attack.getAttack(searchFor);
	  if (attack != null) {
		 out.addOutput(attack.getIRCOutput(0));
	  }
   }

   public void handleMo(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 2) {// only mod off
		 if (command.validate(1, InputType.AMOUNT)) {
			String value = command.get(1);
			Double mo = 0.0;
			try {
			   mo = Double.parseDouble(value.replaceAll("k", ""));
			} catch (NumberFormatException e) {
			   Logger.getLogger("Error").error("", e);
			   return;
			}
			mo = mo * Math.pow(1000, StringTools.countOccurance(value, "k"));
			out.addOutput(editArmy(0, mo.intValue(), 4, 0L, UserCentral.getInstance().getOnlineUser(command.getSender())));
		 }
	  } else if (command.size() == 3) { // only mod off and generals
		 if (command.validate(1, InputType.AMOUNT) && command.validate(2, InputType.GENERAL)) {
			String value = command.get(1);
			Double mo = 0.0;
			try {
			   mo = Double.parseDouble(value.replaceAll("k", ""));
			} catch (NumberFormatException e) {
			   Logger.getLogger("Error").error("", e);
			   return;
			}
			mo = mo * Math.pow(1000, StringTools.countOccurance(value, "k"));
			out.addOutput(editArmy(0, mo.intValue(), Integer.parseInt(command.get(2)), 0L, UserCentral.getInstance()
				  .getOnlineUser(command.getSender())));
		 }
	  } else if (command.size() == 4 && command.isInt(1) && command.validate(2, InputType.AMOUNT)
			&& command.validate(3, InputType.GENERAL)) {
		 // for a specified army, not just the latest added one
		 String value = command.get(2);
		 Double mo = 0.0;
		 try {
			mo = Double.parseDouble(value.replaceAll("k", ""));
		 } catch (NumberFormatException e) {
			Logger.getLogger("Error").error("", e);
			return;
		 }
		 mo = mo * Math.pow(1000, StringTools.countOccurance(value, "k"));
		 out.addOutput(editArmy(Integer.parseInt(command.get(1)), mo.intValue(), Integer.parseInt(command.get(3)), 0L,
			   UserCentral.getInstance().getOnlineUser(command.getSender())));
	  }
   }

   public void handleRemovearmy(final IRCCommand command, final IRCOutput out) {
	  if (command.size() == 2 && command.isInt(1)
			&& Army.deleteFromDB(UserCentral.getInstance().getOnlineUser(command.getSender()), command.get(1))) {
		 out.addOutput("Army " + Integer.parseInt(command.get(1)) + " deleted");
	  }
   }

   /**
    * Used for compiling and formatting returning armies and returns a list to be printed to IRC
    * 
    * @param armyList
    * @param compact
    * @return a List of output strings
    */
   private List<String> printArmiesReturning(final List<Army> armyList, final boolean compact) {
	  List<String> out = new LinkedList<String>();
	  out.add("Armies coming in:");
	  String output = "";
	  NumberFormat nf = NumberFormat.getNumberInstance(Locale.US);
	  nf.setGroupingUsed(false);
	  nf.setMaximumFractionDigits(1);
	  nf.setMinimumFractionDigits(0);
	  for (Army army : armyList) {
		 String nick = army.getMainNick();
		 String gens = army.getGenerals() == 0 ? "" : "(" + army.getGenerals() + "g)";
		 String gain = army.getGain().equals("0") ? "" : "(" + army.getGain() + ")";
		 String mo = army.getMO() == 0 ? "" : "(" + army.getMO() + "mo)";
		 if (compact) {
			Double home = (army.getArmyHome() - GregorianCalendar.getInstance().getTimeInMillis()) / 3600000.0;

			output += Colors.RED + nick + Colors.NORMAL + " (" + nf.format(home) + "hrs)" + Colors.BLUE + mo + Colors.DARK_GREEN
				  + gain + Colors.OLIVE + gens + Colors.NORMAL + " | ";

			if (output.length() > 300) {
			   out.add(output.substring(0, output.length() - 2));
			   output = "";
			}
		 } else {
			String temp = " returns in " + UtopiaTime.compareTimes(army.getArmyHome()) + " ";

			out.add(Colors.RED + nick + ":" + Colors.TEAL + " Army " + army.getArmyNo() + "," + Colors.OLIVE + temp + Colors.BLUE
				  + mo + gain + gens);
		 }
	  }
	  if (output.length() > 1) {
		 out.add(output.substring(0, output.length() - 2));
	  }
	  return out;
   }
}
